using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using ST.Domain;
using ST.Domain.IRepositories;

namespace ST.EntityFrameworkCore.Repositories
{
    public class RepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : Entity<TPrimaryKey>
    {
        protected readonly STDbContext _dbContext;

        public RepositoryBase(STDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<List<TEntity>> GetAllListAsync()
        {
            return await _dbContext.Set<TEntity>().ToListAsync();
        }

        public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await _dbContext.Set<TEntity>().Where(predicate).ToListAsync();  
        }

        public async Task<TEntity> GetAsync(TPrimaryKey id)
        {
            return await _dbContext.Set<TEntity>().FirstOrDefaultAsync(CreateEqualityExpressionForId(id));
        }

        public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await _dbContext.Set<TEntity>().FirstOrDefaultAsync(predicate);
        }

        public async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = true)
        {
            _dbContext.Set<TEntity>().AddAsync(entity);
            if (autoSave)
               await SaveAsync();
            return entity;
        }

        public async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = true)
        {
            var obj = await GetAsync(entity.Id);
            EntityToEntity(entity, obj);
            if (autoSave)
                await SaveAsync();
            return entity;
        }
        
        private void EntityToEntity<T>(T pTargetObjSrc, T pTargetObjDest)
        {
            foreach (var mItem in typeof(T).GetProperties())
            {
                mItem.SetValue(pTargetObjDest, mItem.GetValue(pTargetObjSrc, new object[] { }), null);
            }
        }
        
        public async Task DeleteAsync(TEntity entity, bool autoSave = true)
        {
            _dbContext.Set<TEntity>().Remove(entity);
            if (autoSave)
                await SaveAsync();
        }

        public async Task DeleteAsync(TPrimaryKey id, bool autoSave = true)
        {
            var entity = await GetAsync(id);
            if (entity != null)
            {
                _dbContext.Set<TEntity>().Remove(entity);
                if (autoSave)
                    await SaveAsync();
            }
        }

        public async Task DeleteAsync(Expression<Func<TEntity, bool>> @where, bool autoSave = true)
        {
            var list = await _dbContext.Set<TEntity>().Where(where).ToListAsync();
            if (list != null && list.Any())
            {
                foreach (TEntity entity in list)
                {
                    _dbContext.Set<TEntity>().Remove(entity);
                }
                if (autoSave)
                    await SaveAsync();
            }
            
        }

        public async Task<PagedList<TEntity>> LoadPageListAsync(int startPage, int pageSize, Expression<Func<TEntity, bool>> @where, Expression<Func<TEntity, object>> order)
        {
            var result = from p in _dbContext.Set<TEntity>() select p;
            if (where != null)
                result = result.Where(where);
            
            result = result.OrderBy(order ?? (m => m.Id));

            return await PagedList<TEntity>.CreateAsync(result, startPage, pageSize);
        }

        public async Task<bool> SaveAsync()
        { 
            return await _dbContext.SaveChangesAsync()>0;
        }
        
        /// <summary>
        /// 根据主键构建判断表达式
        /// </summary>
        /// <param name="id">主键</param>
        /// <returns></returns>
        private Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TPrimaryKey id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));
            var lambdaBody = Expression.Equal(
                Expression.PropertyOrField(lambdaParam, "Id"),
                Expression.Constant(id, typeof(TPrimaryKey))
            );

            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }
    }
    
    public abstract class RepositoryBase<TEntity> : RepositoryBase<TEntity, Guid> where TEntity : Entity
    {
        public RepositoryBase(STDbContext dbContext) : base(dbContext)
        {
        }
    }
}